Skip to content

Quartz

介绍文章:Quartz 是什么?一文带你入坑 - 知乎

Quartz 是一个功能强大、成熟且应用广泛的开源作业调度库,它允许按照极其复杂的计划来执行任务。与Spring Boot自带的 @Scheduled注解(适用于简单、静态任务)相比,Quartz 的核心优势在于:

  • 持久化存储:可以将作业、触发器、调度信息存储到数据库中,确保在应用重启后任务状态不丢失,并支持集群部署
  • 动态管理:可以在运行时动态地创建、修改、暂停、恢复和删除任务
  • 灵活调度:通过强大的 Cron表达式 提供极为复杂的调度计划(如“每个工作日上午9:15,但节假日除外”)
  • 集群与故障转移:支持多节点集群运行,实现负载均衡和任务故障自动转移,保证高可用性。

核心对象

在Spring Boot项目中集成Quartz,主要涉及以下几个核心对象:

  • Scheduler(调度器)

    • 核心控制器,由它来绑定JobDetailTrigger,并实际调度任务的执行。
  • Job(任务):定义具体要执行的工作内容。需要创建一个类实现 org.quartz.Job接口,并重写 execute方法

    • Spring集成关键:在Spring Boot中,Job类通常是一个普通的Spring Bean,以便能通过依赖注入使用其他服务(如ServiceRepository)。但需要通过 SpringBeanJobFactory来支持
  • JobDetail(任务详情):定义Job的实例详情。它包含了Job执行时所需的各种属性。一个Job类可以有多个JobDetail实例,每个实例可以有不同的参数

  • Trigger(触发器):定义任务何时被触发执行的规则。最常用的是基于Cron表达式的 CronTrigger

    • 一个JobDetail可以关联多个Trigger,一个Trigger只能关联一个JobDetail

基本使用

application.yml

yaml
spring:
  quartz:
    job-store-type: jdbc # 使用JDBC存储(持久化)。默认为 memory,即内存存储
    jdbc:
      initialize-schema: always # 自动初始化Quartz所需的数据库表(首次启动时使用)
    properties:
      org.quartz.scheduler.instanceId: AUTO # 实例ID自动生成,集群时需要
      org.quartz.jobStore.isClustered: true # 开启集群支持

创建Job:

java
import org.quartz.Job;
import org.quartz.JobExecutionContext;

import java.time.LocalDateTime;

// 实现 Job 接口的类
public class MyDynamicJob implements Job {

    @Override
    public void execute(JobExecutionContext context) {
        System.out.println("执行业务逻辑..." + LocalDateTime.now());
    }
}

动态管理:

java
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Service;

@Service
public class DynamicJobService implements CommandLineRunner {

    @Autowired
    private Scheduler scheduler;

    public void addNewJob(String jobName, String cronExpression) throws SchedulerException {
        // 检查任务是否已存在
        if (scheduler.checkExists(JobKey.jobKey(jobName))) {
            System.out.println("任务已存在: " + jobName);
            return;
        }

        JobDetail jobDetail = JobBuilder.newJob(MyDynamicJob.class)
                .withIdentity(jobName)
                .storeDurably()
                .usingJobData("jobName", jobName) // 可以传递参数
                .build();

        CronTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger-" + jobName)
                .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
                .build();

        scheduler.scheduleJob(jobDetail, trigger);
        System.out.println("任务添加成功: " + jobName + ", Cron: " + cronExpression);

        // 立即打印所有任务
        printAllJobs();
    }

    public void pauseJob(String jobName) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(jobName);
        if (scheduler.checkExists(jobKey)) {
            scheduler.pauseJob(jobKey);
            System.out.println("任务已暂停: " + jobName);
        } else {
            System.out.println("任务不存在: " + jobName);
        }
    }

    @Override
    public void run(String... args) throws Exception {
        try {
            // 延迟3秒,确保调度器完全启动
            Thread.sleep(3000);

            System.out.println("开始添加测试任务...");
            addNewJob("test", "* * * * * ?");

            // 10秒后暂停任务
            new Thread(() -> {
                try {
                    Thread.sleep(10000);
                    pauseJob("test");
                } catch (SchedulerException | InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();

        } catch (SchedulerException | InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void printAllJobs() throws SchedulerException {
        System.out.println("=== 当前所有Quartz任务 ===");
        for (String groupName : scheduler.getJobGroupNames()) {
            for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))) {
                System.out.println("任务: " + jobKey.getName() + ", 组: " + groupName);
            }
        }
        System.out.println("=======================");
    }
}

Ruoyi定时任务

执行策略

  • 立即执行:所有的misfire会马上执行——如果9点misfire,在10:15系统恢复,9点,10点的misfire会马上执行(该策略有可能会造成短期内大量定时任务蜂拥执行
  • 执行一次:所有的合并misfire执行一次——如果9点misfire,在10:15系统恢复,只会执行一次misfire,然后等待下一周期
  • 放弃执行:所有的misfire放弃执行,等待下一周期的任务——如果9点misfire,在10:15系统恢复,需要等待下一周期11点才执行

对应代码:

java
    /**
     * 设置定时任务策略
     */
    public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb)
            throws TaskException
    {
        switch (job.getMisfirePolicy())
        {
            case ScheduleConstants.MISFIRE_DEFAULT:
                return cb;
            case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
                return cb.withMisfireHandlingInstructionIgnoreMisfires();
            case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
                return cb.withMisfireHandlingInstructionFireAndProceed();
            case ScheduleConstants.MISFIRE_DO_NOTHING:
                return cb.withMisfireHandlingInstructionDoNothing();
            default:
                throw new TaskException("The task misfire policy '" + job.getMisfirePolicy()
                        + "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR);
        }
    }